/******************************************************************************
	This file is a part of the Snapshot Bioinformatics Project

	Copyright (C) 2012-2013 Jarom Schow

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

#include "SnapshotUI.h"

#include <QtCore/QEvent>
#include <QtCore/QTextStream>
#include <QtCore/QTime>
#include <QtCore/QCoreApplication>

#include <QtWidgets/QApplication>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QInputDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QSplitter>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTabWidget>
#include <QtWidgets/QVBoxLayout>

#include <QtSql/QSqlError>

#include "GenBankParser.h"
#include "FileUtil.h"
#include "GzFile.h"
#include "Core.h"
#include "Progress.h"
#include "SeqDatabase.h"
#include "ESpell.h"

#include "AppSettings.h"
#include "ConnectDlg.h"
#include "ConnectWiz.h"
#include "FastaOptionsDlg.h"
#include "Snapshot.h"
#include "SnapshotGeneCollection.h"
#include "SnapshotGeneModel.h"
#include "SnapshotInfoDlg.h"
#include "SnapshotPrefsDlg.h"
#include "SnapshotTree.h"
#include "SnapshotTreeModel.h"
#include "SnapshotTreeView.h"
#include "SnapshotFileLoader.h"

const int	c_defaultWidth = 600;
const int	c_defaultHeight = 600;
const int	c_btnSize = 25;

const QString	c_versionDesignation( "Beta" );

const QString	c_genBankSeqDB( "GenBank" );
const QString	c_genBankSeqDBAuth( "NCBI GenBank" );
const QString	c_genBankSeqDBDesc( "Sequence data loaded from NCBI GenBank" );

const QString	c_snapshotWikiURL( "http://code.google.com/p/snapshot-bioinformatics/wiki/Manual" );
const QString	c_snapshotQuickStartURL( "http://code.google.com/p/snapshot-bioinformatics/wiki/QuickStart" );
const QString	c_snapshotResourceImg( ":/images/Snapshot.png" );

/**
**/
SnapshotUI::SnapshotUI() : m_server( NULL )
{
	QSettings	settings;

	settings.beginGroup( "Preferences" );

	setWindowTitle( qApp->applicationName() + " " + qApp->applicationVersion() + " " + c_versionDesignation );

	m_taxonomy = new SnapshotTree;
	m_taxonomy->setIgnoreEmpty( settings.value( "IgnoreEmpty", true ).toBool() );
	m_taxonomy->setIgnoreEnvSamp( settings.value( "IgnoreEnvSamp", true ).toBool() );
	m_taxonomy->setIgnoreUnclassified( settings.value( "IgnoreUnclassified", true ).toBool() );

	m_genes = new SnapshotGeneCollection;

	connect( m_genes, SIGNAL(selectionChanged()), this, SLOT(updateCoverages()) );

	buildMainMenu();
	buildUI();
}

/**
**/
SnapshotUI::~SnapshotUI()
{
	QSettings	settings;

	settings.beginGroup( "Preferences" );

	settings.setValue( "IgnoreEmpty", m_taxonomy->getIgnoreEmpty() );
	settings.setValue( "IgnoreEnvSamp", m_taxonomy->getIgnoreEnvSamp() );
	settings.setValue( "IgnoreUnclassified", m_taxonomy->getIgnoreUnclassified() );

	delete m_taxonomy;
	delete m_server;
	delete m_genes;
}

/**
**/
bool SnapshotUI::getIgnoreEmpty() const
{
	return m_taxonomy->getIgnoreEmpty();
}

/**
**/
void SnapshotUI::setIgnoreEmpty( bool onOff )
{
	m_taxonomy->setIgnoreEmpty( onOff );
}

/**
**/
bool SnapshotUI::getIgnoreUnclassified() const
{
	return m_taxonomy->getIgnoreUnclassified();
}

/**
**/
void SnapshotUI::setIgnoreUnclassified( bool onOff )
{
	m_taxonomy->setIgnoreUnclassified( onOff );
}

/**
**/
bool SnapshotUI::getIgnoreEnvSamp() const
{
	return m_taxonomy->getIgnoreEnvSamp();
}

/**
**/
void SnapshotUI::setIgnoreEnvSamp( bool onOff )
{
	m_taxonomy->setIgnoreEnvSamp( onOff );
}

/**
**/
SnapshotTree* SnapshotUI::getTaxonomy() const
{
	return m_taxonomy;
}

/**
**/
SnapshotGeneCollection* SnapshotUI::getGenes() const
{
	return m_genes;
}

/**
**/
void SnapshotUI::dbSettings()
{
	Bio::SqlServerSettings	settings;

	AppSettings::loadDBSettings( settings );

	if( settings.getConfigurationFile().isEmpty() )
		settings.setConfigurationFile( getDefaultEmbeddedConfigFile() );

	ConnectDlg	dlg( this, settings );

	if( dlg.exec() != QDialog::Accepted )
		return;

	settings = dlg.getServerSettings();

	if( !connectToDB( settings ) ){
		QMessageBox::critical( this, "Connect to Database", "One or more errors occurred while connecting to the database. "
			"See the log file for details." );
		return;
	}

	AppSettings::saveDBSettings( settings );
}

/**
**/
void SnapshotUI::dbWizard()
{
	Bio::GenBank	genBank;

	ConnectWiz	wizard( this, &genBank, getDefaultEmbeddedConfigFile() );

	if( wizard.exec() != QDialog::Accepted )
		return;

	Bio::SqlServerSettings	settings = wizard.getServerSettings();

	if( !wizard.isCreate() ){
		if( !connectToDB( settings ) ){
			QMessageBox::critical( this, "Connect to Database", "One or more errors occurred while connecting to the database. "
				"See the log file for details." );
			return;
		}

		AppSettings::saveDBSettings( settings );
		return;
	}

	Bio::Progress	progress( "Creating Database..." );

	if( !createDB( settings ) ){
		progress.finish();
		QMessageBox::critical( this, "Create Database", "One or more errors occurred while creating the database. "
			"See the log file for details." );
		return;
	}

	// Save database settings once the database is successfully created
	AppSettings::saveDBSettings( settings );

	progress.status( "Loading Taxonomy..." );

	if( !m_server->updateTaxonomy( g_snapshot->getAppDataPath() ) ){
		progress.finish();
		QMessageBox::critical( this, "Load Taxonomy", "One or more errors occurred while updating the taxonomy. "
			"See the log file for details." );
		return;
	}
	
	progress.status( "Loading GenBank Data..." );

	if( !m_server->createSeqDatabase( c_genBankSeqDB, c_genBankSeqDBAuth, c_genBankSeqDBDesc ) ){
		progress.finish();
		QMessageBox::critical( this, "Load GenBank Data", "One or more errors occurred while creating a new sequence database. "
			"See the log file for details." );
		return;
	}

	Bio::SeqDatabase	seqDB = m_server->getSeqDatabase( c_genBankSeqDB );
	SnapshotFileLoader	loader( seqDB );

	for( int i = 0; i < Bio::GenBank::NumDivisions; i++ ){
		Bio::GenBank::Division	div = Bio::GenBank::Division( i );

		if( !wizard.isDivisionSelected( div ) )
			continue;

		int	numFiles = genBank.getNumFiles( div );

		for( int j = 0; j < numFiles; j++ ){
			loader.addFile( genBank.getFileUrl( div, j ) );
		}
	}

	if( !loader.loadAllFiles() ){
		progress.finish();
		QMessageBox::critical( this, "Load GenBank Data", "One or more errors occurred while loading data files into the database. "
			"See the log file for details." );
		return;
	}

	progress.finish();
}

/**
**/
void SnapshotUI::loadSeqFiles()
{
	QStringList	filenames = QFileDialog::getOpenFileNames( this, "Select one or more sequence files to load", "D:/Seq/GenBank" );

	if( filenames.isEmpty() )
		return;

	int				idx = 1;
	bool			status = true;
	QTime			timer;
	Bio::Progress	topLevel( "Loading sequence files...", 0, true );
	Bio::Progress	progress( "", filenames.count() );

	timer.start();

	QStringListIterator	fileIt( filenames );

	while( fileIt.hasNext() ){
		QString	filename = fileIt.next();

		progress.status( QString( "Loading file %1 (%2/%3)..." ).arg( Bio::FileUtil::getFilename( filename ) )
			.arg( idx++ ).arg( filenames.count() ) );
		if( !loadSeqFile( filename ) )
			status = false;
		progress.step();
		if( progress.isCanceled() ){
			Bio::Core::log( "Sequence file load cancelled by user." );
			status = false;
			break;
		}
	}

	progress.finish();

	int elapsed = timer.elapsed() / 100;

	if( status ){
		Bio::Core::log( QString( "%1 file(s) loaded in %2 min %3.%4 sec." ).arg( filenames.count() )
			.arg( elapsed / 600 ).arg( (elapsed % 600) / 10 ).arg( elapsed % 10 ) );

		QMessageBox::information( this, "Finished loading records", QString( "Loaded %1 file(s) in %2 min %3.%4 sec." )
			.arg( filenames.count() ).arg( elapsed / 600 ).arg( (elapsed % 600) / 10 ).arg( elapsed % 10 ) );
	}
	else {
		QMessageBox::critical( this, "Error loading records", "One or more database errors occurred, see the log file for details." );
		Bio::Core::log( "Errors occurred while loading files." );
	}
}

/**
**/
void SnapshotUI::loadGenBank()
{
	Bio::GenBank	genBank;
	GenBankWiz		wizard( this, &genBank );

	if( wizard.exec() != QDialog::Accepted )
		return;
	
	Bio::Progress		progress( "Loading GenBank Data..." );
	Bio::SeqDatabase	seqDB = m_server->getSeqDatabase( c_genBankSeqDB );

	if( !seqDB.isValid() ){
		if( !m_server->createSeqDatabase( c_genBankSeqDB, c_genBankSeqDBAuth, c_genBankSeqDBDesc ) ){
			progress.finish();
			QMessageBox::critical( this, "Load GenBank Data", "One or more errors occurred while creating a new sequence database. "
				"See the log file for details." );
			return;
		}

		seqDB = m_server->getSeqDatabase( c_genBankSeqDB );
	}

	SnapshotFileLoader	loader( seqDB );

	for( int i = 0; i < Bio::GenBank::NumDivisions; i++ ){
		Bio::GenBank::Division	div = Bio::GenBank::Division( i );

		if( !wizard.isDivisionSelected( div ) )
			continue;

		int	numFiles = genBank.getNumFiles( div );

		for( int j = 0; j < numFiles; j++ ){
			loader.addFile( genBank.getFileUrl( div, j ) );
		}
	}

	if( !loader.loadAllFiles() ){
		progress.finish();
		QMessageBox::critical( this, "Load GenBank Data", "One or more errors occurred while loading data files into the database. "
			"See the log file for details." );
		return;
	}

	progress.finish();
}

/**
**/
void SnapshotUI::updateTaxonomy()
{
	if( !m_server ){
		QMessageBox::critical( this, "Update Taxonomy", "You must be connected to a database to update the taxonomy." );
		return;
	}

	Bio::Progress	progress( "Updating Taxonomy...", 0, true );

	bool	status = m_server->updateTaxonomy( g_snapshot->getAppDataPath() );

	progress.finish();

	if( !status )
		QMessageBox::critical( this, "Update Taxonomy", "One or more errors occurred while performing update. "
			"See the log file for details." );
}

/**
**/
void SnapshotUI::addTaxon()
{
	if( !m_server ){
		QMessageBox::critical( this, "Database Error", "You must be connected to a database." );
		return;
	}

	int	taxonId = getTaxonFromUser();

	if( taxonId < 0 )
		return;

	Bio::Progress	progress( "Adding Taxa..." );

	if( !m_taxonomy->addFromDatabase( taxonId, *m_server, m_genes ) ) {
		QMessageBox::critical( this, "Database Error", "There was an error loading the taxonomy from the database. "
			"See the log file for details." );
	}

	progress.finish();
}

/**
**/
void SnapshotUI::clearAll()
{
	m_taxonomy->clear();
	m_genes->clear();
}

/**
**/
void SnapshotUI::exportSequences()
{
	if( !m_server )
		return;

	QSettings	settings;
	QString		dirname = settings.value( "LastFASTADir" ).toString();
	
	dirname = QFileDialog::getExistingDirectory( this, "Select a directory for export", dirname );

	if( dirname.isEmpty() )
		return;

	FastaOptionsDlg	dlg( this );

	if( dlg.exec() != QDialog::Accepted )
		return;

	bool	saveName = dlg.saveTaxonName();
	bool	saveGI = dlg.saveGBIdentifier();
	bool	saveGA = dlg.saveGBAccession();

	Bio::Core::log( dirname );

	Bio::Progress	progress( "Exporting FASTA files...", m_genes->getNumSelectedGenes() );

	QListIterator<SnapshotGene*>	geneIt( m_genes->getGeneIterator() );

	while( geneIt.hasNext() ){
		SnapshotGene	*gene = geneIt.next();

		if( gene->isSelected() ){
			gene->saveAs( dirname + '/' + gene->getName() + ".fas", *m_server, saveName, saveGI, saveGA );
			progress.step();
		}
	}

	settings.setValue( "LastFASTADir", dirname );

	progress.finish();
}

/**
**/
void SnapshotUI::exportAccessions()
{
	QSettings	settings;
	QString		dirname = settings.value( "LastAccessionDir" ).toString();
	QString		filename = QFileDialog::getSaveFileName( this, "Save Accessions Table", dirname, 
							"Text files (*.txt)" );

	if( filename.isEmpty() )
		return;

	QFile	file( filename );

	if( !file.open( QIODevice::WriteOnly ) ){
		Bio::Core::error( QString( "Could not open file '%1' for writing." ).arg( filename ) );
		return;
	}

	QTextStream		os( &file );
	Bio::Progress	progress( "Exporting Accessions table..." );

	QListIterator<SnapshotGene*>	geneIt( m_genes->getGeneIterator() );

	os << "Taxon";

	while( geneIt.hasNext() ){
		SnapshotGene	*gene = geneIt.next();

		if( gene->isSelected() )
			os << ',' << gene->getName();
	}

	os << '\n';

	QList<SnapshotNode*>			taxa = m_taxonomy->getAllTaxa(); //( "genus" );
	QListIterator<SnapshotNode*>	taxIt( taxa );

	while( taxIt.hasNext() ){
		SnapshotNode	*taxon = taxIt.next();

		os << taxon->getScientificName();

		geneIt.toFront();

		while( geneIt.hasNext() ){
			SnapshotGene	*gene = geneIt.next();

			if( !gene->isSelected() )
				continue;

			os << ',';

			SnapshotSequences* seqs = gene->getSequences( taxon->getTaxonId() );

			if( !seqs || seqs->getNumSequences() < 1 )
				continue;

			os << seqs->getSequence( 0 )->getAccession();
		}

		os << '\n';
	}

	file.close();

	settings.setValue( "LastAccessionDir", Bio::FileUtil::getPath( filename ) );

	progress.finish();
}

/**
**/
void SnapshotUI::updateCoverages()
{
	m_taxonomy->updateGeneCoverage( m_genes->getNumSelectedGenes() );
}

/**
**/
void SnapshotUI::about()
{
	QDialog		aboutDlg( this );
	QVBoxLayout	*layout = new QVBoxLayout;
	QLabel		*wgt = new QLabel( &aboutDlg );
	QLabel		*lbl;
	QPixmap		pix( c_snapshotResourceImg );

	aboutDlg.setWindowTitle( tr( "About Snapshot" ) );

	wgt->setPixmap( pix );
	wgt->setAlignment( Qt::AlignCenter );
	aboutDlg.setLayout( layout );
	layout->addWidget( wgt );

	lbl = new QLabel( &aboutDlg );
	lbl->setAlignment( Qt::AlignCenter );
	layout->addWidget( lbl );
	lbl->setText( qApp->applicationName() + " version " + g_snapshot->getVersion().getVersionString( true ) + " " + c_versionDesignation );

	lbl = new QLabel( &aboutDlg );
	lbl->setAlignment( Qt::AlignCenter );
	layout->addWidget( lbl );
	lbl->setText( tr( "Copyright (C) 2012-2013 Jarom Schow" ) );

	lbl = new QLabel( &aboutDlg );
	lbl->setAlignment( Qt::AlignCenter );
	layout->addWidget( lbl );
	lbl->setText( tr( "Licensed under the <A HREF=\"http://www.gnu.org/licenses/\">GPLv3</A>" ) );
	connect( lbl, SIGNAL(linkActivated(const QString&)), this, SLOT(followLink(const QString&)) );

#ifdef Q_OS_MAC
	// this is necessary since the Mac is not displaying a close button for this dialog.
	QHBoxLayout *hbox = new QHBoxLayout;

	hbox->addStretch( 100 );

	QPushButton *btn = new QPushButton( tr( "Ok" ) );

	hbox->addWidget( btn );
	connect( btn, SIGNAL(clicked()), &aboutDlg, SLOT(accept()) );
	layout->addLayout( hbox );
#endif

	aboutDlg.exec();
}

/**
**/
void SnapshotUI::goWiki()
{
	g_snapshot->showURL( c_snapshotWikiURL );
}

/**
**/
void SnapshotUI::goQuickStart()
{
	g_snapshot->showURL( c_snapshotQuickStartURL );
}

/**
**/
void SnapshotUI::viewLogFile()
{
	Bio::Core::flushLog();
	g_snapshot->showURL( QString( "file://%1/log.txt" ).arg( g_snapshot->getAppDataPath() ) );
}

/**
**/
void SnapshotUI::preferences()
{
	SnapshotPrefsDlg	dlg( this );

	dlg.exec();
}

/**
**/
void SnapshotUI::information()
{
	SnapshotInfoDlg	dlg( this );

	dlg.exec();
}

/**
**/
void SnapshotUI::followLink( const QString &url )
{
	g_snapshot->showURL( url );
}

/**
**/
bool SnapshotUI::event( QEvent *e )
{
	if( e->type() != QEvent::User )
		return QMainWindow::event( e );

	Bio::SqlServerSettings	settings;

	qApp->processEvents();

	if( !AppSettings::loadDBSettings( settings ) )
		dbWizard();
	else {
		if( settings.getServerType() == Bio::SqlServerSettings::MySQLEmbedded && settings.getConfigurationFile().isEmpty() )
			settings.setConfigurationFile( getDefaultEmbeddedConfigFile() );

		if( !connectToDB( settings ) )
			dbSettings();
	}

	return true;
}

/**
**/
void SnapshotUI::closeEvent( QCloseEvent *e )
{
	saveUI();
	QMainWindow::closeEvent( e );
}

/**
**/
void SnapshotUI::init()
{
	restoreUI();
	qApp->postEvent( this, new QEvent( QEvent::User ) );
}

/**
**/
void SnapshotUI::buildMainMenu()
{
	QMenuBar	*bar = menuBar();
	QMenu		*menu;

	menu = bar->addMenu( tr( "&File" ) );
	menu->addAction( tr( "Clear All Results" ), this, SLOT(clearAll()) );
	menu->addSeparator();
	menu->addAction( tr( "Export FASTA files..." ), this, SLOT(exportSequences()) );
	menu->addSeparator();
	menu->addAction( tr( "Export Accession table..." ), this, SLOT(exportAccessions()) );
	menu->addSeparator();
	menu->addAction( tr( "Exit" ), this, SLOT(close()) );

	menu = bar->addMenu( tr( "&Database" ) );
	menu->addAction( tr( "Database Connection Settings..." ), this, SLOT(dbSettings()) );
	menu->addAction( tr( "Database Connection Wizard..." ), this, SLOT(dbWizard()) );
	menu->addSeparator();
	menu->addAction( tr( "Load sequences from file..." ), this, SLOT(loadSeqFiles()) );
	menu->addAction( tr( "Load sequences from GenBank..." ), this, SLOT(loadGenBank()) );
	menu->addAction( tr( "Update Taxonomy" ), this, SLOT(updateTaxonomy()) );

	menu = bar->addMenu( tr( "&Edit" ) );
	menu->addAction( tr( "Information..." ), this, SLOT(information()) );
	menu->addSeparator();
	menu->addAction( tr( "Preferences..." ), this, SLOT(preferences()) );

	menu = bar->addMenu( tr( "&Help" ) );
	menu->addAction( tr( "Snapshot Wiki..." ), this, SLOT(goWiki()) );
	menu->addAction( tr( "Quick start..." ), this, SLOT(goQuickStart()) );
	menu->addAction( tr( "About Snapshot" ), this, SLOT(about()) );
	menu->addSeparator();
	menu->addAction( tr( "View log file..." ), this, SLOT(viewLogFile()) );
}

/**
**/
void SnapshotUI::buildUI()
{
	QLabel		*lbl;
	QWidget		*wgt;
	QVBoxLayout *layout;
	QHBoxLayout	*hlayout;

	m_splitter = new QSplitter( this );
	m_splitter->setStyleSheet( "QSplitter::handle {\n"
								"border: 2px solid grey;\n"
								"border-radius: 5px;\n"
								"}" );

	setCentralWidget( m_splitter );

	wgt = new QWidget;
	layout = new QVBoxLayout( wgt );
	m_splitter->addWidget( wgt );

	hlayout = new QHBoxLayout;
	hlayout->setMargin( 0 );
	layout->addLayout( hlayout );

	lbl = new QLabel( "Taxonomy Browser:", wgt );
	hlayout->addWidget( lbl, 100 );

	m_addBtn = new QPushButton( "+", wgt );
	m_addBtn->setFixedWidth( c_btnSize );
	hlayout->addWidget( m_addBtn );
	connect( m_addBtn, SIGNAL(clicked()), this, SLOT(addTaxon()) );

	m_taxonView = new SnapshotTreeView( wgt );
	m_taxonModel = new SnapshotTreeProxyModel;
	m_taxonModel->setTree( m_taxonomy );
	m_taxonView->setModel( m_taxonModel );
	layout->addWidget( m_taxonView );

	wgt = new QWidget;
	layout = new QVBoxLayout( wgt );
	m_splitter->addWidget( wgt );

	lbl = new QLabel( "Gene Browser:", wgt );
	layout->addWidget( lbl );

	m_geneView = new QTreeView( wgt );
	m_geneModel = new SnapshotGeneProxyModel;
	m_geneModel->setGenes( m_genes );
	m_geneView->setModel( m_geneModel );
	m_geneView->sortByColumn( 1 );
	m_geneView->setSortingEnabled( true );
	layout->addWidget( m_geneView );

	m_dbStatusLbl = new QLabel( tr( "Connecting..." ) );
	statusBar()->addPermanentWidget( m_dbStatusLbl );

	setOnlineHelp();
}

/**
**/
void SnapshotUI::setOnlineHelp()
{
	m_addBtn->setToolTip( "Click to add taxa to the taxonomy browser." );

	m_splitter->setToolTip( "Click and drag to resize panes." );

	m_dbStatusLbl->setToolTip( "Shows the currently connected database." );

	m_taxonView->setToolTip( "Shows all the currently loaded taxa." );

	m_geneView->setToolTip( "Shows the genes available for the currently loaded taxa." );
}

/**
**/
void SnapshotUI::restoreUI()
{
	bool		maximized;
	QSettings	settings;

	settings.beginGroup( "UserInterface" );

	maximized = settings.value( "Maximized", false ).toBool();
	if( maximized )
		showMaximized();

	int	width, height, left, top;
	int	taxSize, geneSize, taxCol1, taxCol2, geneCol1, geneCol2;

	width = settings.value( "Width", c_defaultWidth ).toInt();
	height = settings.value( "Height", c_defaultHeight ).toInt();
	left = settings.value( "Left", -1 ).toInt();
	top = settings.value( "Top", -1 ).toInt();
	
	taxSize = settings.value( "TaxSize", -1 ).toInt();
	taxCol1 = settings.value( "TaxCol1", -1 ).toInt();
	taxCol2 = settings.value( "TaxCol2", -1 ).toInt();

	geneSize = settings.value( "GeneSize", -1 ).toInt();
	geneCol1 = settings.value( "GeneCol1", -1 ).toInt();
	geneCol2 = settings.value( "GeneCol2", -1 ).toInt();

	if( left < 0 || top < 0 )
		resize( width, height );
	else
		setGeometry( left, top, width, height );

	if( taxSize > 0 && geneSize > 0 ){
		QList<int>	sizes;

		sizes.append( taxSize );
		sizes.append( geneSize );

		m_splitter->setSizes( sizes );

		if( taxCol1 > 0 && taxCol2 > 0 ){
			m_taxonView->setColumnWidth( 0, taxCol1 );
			m_taxonView->setColumnWidth( 1, taxCol2 );
		}

		if( geneCol1 > 0 && geneCol2 > 0 ){
			m_geneView->setColumnWidth( 0, geneCol1 );
			m_geneView->setColumnWidth( 1, geneCol2 );
		}
	}
}

/**
**/
void SnapshotUI::saveUI()
{
	QRect		geom = geometry();
	QSettings	settings;
	QList<int>	sizes = m_splitter->sizes();

	settings.beginGroup( "UserInterface" );

	settings.setValue( "Maximized", isMaximized() );
	settings.setValue( "Width", geom.width() );
	settings.setValue( "Height", geom.height() );
	settings.setValue( "Left", geom.left() );
	settings.setValue( "Top", geom.top() );
	settings.setValue( "TaxSize", sizes[0] );
	settings.setValue( "GeneSize", sizes[1] );

	settings.setValue( "TaxCol1", m_taxonView->columnWidth( 0 ) );
	settings.setValue( "TaxCol2", m_taxonView->columnWidth( 1 ) );

	settings.setValue( "GeneCol1", m_geneView->columnWidth( 0 ) );
	settings.setValue( "GeneCol2", m_geneView->columnWidth( 1 ) );
}

/**
**/
int SnapshotUI::getTaxonFromUser()
{
	QString	taxon = QInputDialog::getText( this, "Add Taxon", "Enter the name of a taxon to add:" );

	if( taxon.isEmpty() )
		return -1;

	int	taxonId = findTaxonByName( taxon );

	if( taxonId > 0 )
		return taxonId;

	Bio::ESpell	spell( "taxonomy", taxon );

	if( !spell.exec() || spell.getNumCorrections() < 1 ){
		QMessageBox::critical( this, "Taxonomy Search", QString( "No taxon could be found for '%1'." ).arg( taxon ) );
		return -1;
	}

	taxon = QInputDialog::getText( this, "Add Taxon", QString( "Could not find '%1'. Did you mean:" ).arg( taxon ), 
				QLineEdit::Normal, spell.getCorrection( 0 ) );

	if( taxon.isEmpty() )
		return -1;

	taxonId = findTaxonByName( taxon );

	if( taxonId > 0 )
		return taxonId;

	QMessageBox::critical( this, "Taxonomy Search", QString( "No taxon could be found for '%1'." ).arg( taxon ) );

	return -1;
}

/**
**/
int SnapshotUI::findTaxonByName( const QString &name )
{
	if( !m_server )
		return -1;

	QString		sql = "SELECT taxon_id FROM taxon_name WHERE name = '%1'";

	if( !m_server->exec( sql.arg( name ) ) )
		return -1;

	QSqlQuery	query = m_server->getQuery();

	if( query.first() )
		return query.value( 0 ).toInt();

	// try again with capitalization
	QString	name2 = name.toLower();

	name2[0] = name2[0].toUpper();
	
	if( !m_server->exec( sql.arg( name2 ) ) )
		return -1;

	query = m_server->getQuery();

	if( query.first() )
		return query.value( 0 ).toInt();

	return -1;
}
	
/**
**/
bool SnapshotUI::connectToDB( Bio::SqlServerSettings settings )
{
	delete m_server;
	m_server = new Bio::SqlServer;

	if( settings.getServerType() == Bio::SqlServerSettings::MySQLEmbedded ){
		if( !checkEmbeddedConfigFile( settings.getConfigurationFile() ) )
			return false;
		settings.setDatabaseName( "biosql" );
	}

	if( !m_server->connect( settings ) ){
		delete m_server;
		m_server = NULL;
		m_dbStatusLbl->setText( tr( "Not Connected" ) );
		return false;
	}

	m_dbStatusLbl->setText( tr( "Connected" ) );

	return true;
}

/**
**/
bool SnapshotUI::createDB( Bio::SqlServerSettings settings )
{
	delete m_server;
	m_server = new Bio::SqlServer;

	if( settings.getServerType() == Bio::SqlServerSettings::MySQLEmbedded ){
		if( !checkEmbeddedConfigFile( settings.getConfigurationFile() ) )
			return false;
		settings.setDatabaseName( "biosql" );
	}

	if( !m_server->create( settings ) ){
		delete m_server;
		m_server = NULL;
		m_dbStatusLbl->setText( tr( "Not Connected" ) );
		return false;
	}

	m_dbStatusLbl->setText( tr( "Connected" ) );

	return true;
}

/**
**/
bool SnapshotUI::checkEmbeddedConfigFile( const QString &filename )
{
	QString	mySQLPath = Bio::FileUtil::getPath( filename );
	QString	dataDir = mySQLPath + "/data";
	QDir	dir( dataDir );

	if( !dir.exists() ){
		if( !dir.mkpath( dataDir ) )
			return false;
	}

	QFile	file( filename );

	if( !file.exists() ){
		if( !file.open( QIODevice::WriteOnly ) )
			return false;

		QByteArray	basePath = qApp->applicationDirPath().toLatin1();
		QByteArray	dataPath = mySQLPath.toLatin1() + "/data";

#ifdef Q_OS_MAC
		basePath += "/../Resources";
#endif

		file.write( "[embedded]\nbasedir=" );
		file.write( basePath );
		file.write( "\ndatadir=" );
		file.write( dataPath );
		file.putChar( '\n' );
		file.close();
	}

	return true;
}

/**
**/
QString SnapshotUI::getDefaultEmbeddedConfigFile() const
{
	return g_snapshot->getAppDataPath() + "/MySQL/my.ini";
}

/**
**/
bool SnapshotUI::loadSeqFile( const QString &filename )
{
	bool				status = true;
	Bio::GenBankParser	parser;
	QTime				timer;

	Bio::Core::log( "Reading sequence file " + filename );

	timer.start();

	if( filename.right( 3 ).toLower() == ".gz" ){
		Bio::GzFile	file( filename );
		if( !file.open( QIODevice::ReadOnly ) ){
			QString	msg = QString( "Could not open file '%1' for reading." ).arg( filename );
			QMessageBox::critical( this, "Error opening file", msg );
			Bio::Core::log( msg );
			return false;
		}
		status = parser.parse( file );
		file.close();
	}
	else {
		QFile	file( filename );
		if( !file.open( QIODevice::ReadOnly ) ){
			QString	msg = QString( "Could not open file '%1' for reading." ).arg( filename );
			QMessageBox::critical( this, "Error opening file", msg );
			Bio::Core::log( msg );
			return false;
		}
		status = parser.parse( file );
		file.close();
	}

	int elapsed1 = timer.elapsed() / 100;
	int	nRecords = parser.getNumRecords();

	if( status ){
		Bio::Core::log( QString( "%1 records read from file in %2 min %3.%4 sec." ).arg( nRecords )
			.arg( elapsed1 / 600 ).arg( (elapsed1 % 600) / 10 ).arg( elapsed1 % 10 ) );
	}
	else {
		QMessageBox::critical( this, "Error reading file", "One or more parser errors occurred, see the log file for details." );
		Bio::Core::log( "Error reading file." );
		return false;
	}

	Bio::SeqDatabase	db = m_server->getSeqDatabase( "GenBank" );

	if( !db.isValid() ){
		QMessageBox::critical( this, "Error loading records", "The sequence database is not valid." );
		Bio::Core::log( "Error loading records: the sequence database is not valid." );
		return false;
	}

	Bio::Core::log( "Loading records into database..." );

	timer.restart();

	status = db.load( &parser );
	if( !status ){
		//QMessageBox::critical( this, "Error loading records", "One or more database errors occurred, see the log file for details." );
		Bio::Core::log( "Error loading records into database." );
		return false;
	}

	int	elapsed2 = timer.elapsed() / 100;

	Bio::Core::log( QString( "Loaded %1 records into database in %2 min %3.%4 sec." ).arg( nRecords )
		.arg( elapsed2 / 600 ).arg( (elapsed2 % 600) / 10 ).arg( elapsed2 % 10 ) );

	return true;
}
